package com.wenjie.databseDiff.handle.service.impl;

import cn.hutool.json.JSONUtil;
import com.mybatisflex.core.query.QueryWrapper;
import com.wenjie.databseDiff.entity.CompareConfig;
import com.wenjie.databseDiff.entity.DbDiffReq;
import com.wenjie.databseDiff.entity.ResultData;
import com.wenjie.databseDiff.enums.ColumnAttr;
import com.wenjie.databseDiff.enums.DatasourceAttr;
import com.wenjie.databseDiff.enums.DiffType;
import com.wenjie.databseDiff.enums.TableAttr;
import com.wenjie.databseDiff.handle.service.DbDifService;
import com.wenjie.original.entity.SysDiffColumn;
import com.wenjie.original.entity.SysDiffDatasource;
import com.wenjie.original.entity.SysDiffTable;
import com.wenjie.original.entity.table.SysDiffTableTableDef;
import com.wenjie.original.service.ISysDiffDatasourceService;
import com.wenjie.original.service.ISysDiffTableService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author wxg
 */
@Service
@Slf4j
public class DbDifServiceImpl implements DbDifService {
    private final ISysDiffDatasourceService datasourceService;

    private final ISysDiffTableService tableInfoService;

    private ThreadLocal<CompareConfig> currCompareConfig = new ThreadLocal<>();

    public DbDifServiceImpl(ISysDiffDatasourceService datasourceService, ISysDiffTableService tableInfoService) {
        this.datasourceService = datasourceService;
        this.tableInfoService = tableInfoService;
    }


    @Override
    public ResultData dif(DbDiffReq req) {
        Long ds1 = req.getDatasourceId1();
        Long ds2 = req.getDatasourceId2();
        CompareConfig compareConfig = Optional.ofNullable(req.getCompareConfig()).orElse(new CompareConfig());
        currCompareConfig.set(compareConfig);
        //通过id加载数据源配置
        SysDiffDatasource datasource1 = datasourceService.getById(ds1);
        SysDiffDatasource datasource2 = datasourceService.getById(ds2);

        return calc(datasource1, datasource2);
    }


    private ResultData calc(SysDiffDatasource d1, SysDiffDatasource d2) {
        ResultData resultData = new ResultData();

        //数据源差异结果
        ResultData.DatasourceDiff datasourceDiff = calcDatasourceDiff(d1, d2);
        resultData.setDatasourceDiff(datasourceDiff);

        //表差异结果
        List<ResultData.TableDiff> tableDiffs = buildTableDiff(d1.getSysDiffDatasourceId(), d2.getSysDiffDatasourceId());
        resultData.setTableDiffs(tableDiffs);

        ResultData.DatasourceSummary summary = buildSummary(datasourceDiff, tableDiffs);
        resultData.setSummary(summary);
        return resultData;
    }


    private ResultData.DatasourceSummary buildSummary(ResultData.DatasourceDiff datasourceDiff, List<ResultData.TableDiff> tableDiffs) {
        ResultData.DatasourceSummary summary = new ResultData.DatasourceSummary();
        summary.setLeftTableCount(datasourceDiff.getLeft().getTableCount());
        summary.setRightTableCount(datasourceDiff.getRight().getTableCount());
        summary.setDiffCount(tableDiffs.stream().mapToInt(ResultData.TableDiff::getDiffCount).sum());
        int lackTableCount = 0;
        int diffTableCount = 0;
        int leftColumnLackCount = 0;
        int leftColumnDiffCount = 0;
        int rightColumnLackCount = 0;
        int rightColumnDiffCount = 0;
        for (ResultData.TableDiff tableDiff : tableDiffs) {
            if (tableDiff.getDiffType() == DiffType.LACK) {
                lackTableCount++;
            }
            if (tableDiff.getDiffType() == DiffType.DIFF) {
                diffTableCount++;
            }
            List<ResultData.ColumnDiff> leftDiffs = tableDiff.getLeft().getColumnDiffs();
            List<ResultData.ColumnDiff> rightDiffs = tableDiff.getRight().getColumnDiffs();
            leftColumnLackCount += (int) leftDiffs.stream().filter(v -> v.getDiffType() == DiffType.LACK && v.getLeft() != null && v.getLeft().getColumnName() == null).count();
            rightColumnLackCount += (int) rightDiffs.stream().filter(v -> v.getDiffType() == DiffType.LACK && v.getRight() != null && v.getRight().getColumnName() == null).count();
            leftColumnDiffCount += leftDiffs.stream().filter(v -> v.getDiffType() == DiffType.DIFF).mapToInt(ResultData.ColumnDiff::getDiffCount).sum();
            rightColumnDiffCount += rightDiffs.stream().filter(v -> v.getDiffType() == DiffType.DIFF).mapToInt(ResultData.ColumnDiff::getDiffCount).sum();
        }
        summary.setLackTableCount(lackTableCount);
        summary.setDiffTableCount(diffTableCount);
        summary.setLeftColumnLackCount(leftColumnLackCount);
        summary.setLeftColumnDiffCount(leftColumnDiffCount);
        summary.setRightColumnLackCount(rightColumnLackCount);
        summary.setRightColumnDiffCount(rightColumnDiffCount);
        return summary;
    }

    private List<ResultData.TableDiff> buildTableDiff(Long datasourceId1, Long datasourceId2) {
        List<ResultData.TableDiff> tableDiffs = new ArrayList<>();

        //获取表信息
        List<SysDiffTable> tables1 = datasourceService.getTableInfos(datasourceId1);
        List<SysDiffTable> tables2 = datasourceService.getTableInfos(datasourceId2);

        //获取列信息
        List<SysDiffColumn> columnInfos1 = datasourceService.getColumnInfos(datasourceId1);
        List<SysDiffColumn> columnInfos2 = datasourceService.getColumnInfos(datasourceId2);

        //对列 按表名称分组
        Map<String, List<SysDiffColumn>> group1 = columnInfos1.stream().collect(Collectors.groupingBy(SysDiffColumn::getTableName));
        Map<String, List<SysDiffColumn>> group2 = columnInfos2.stream().collect(Collectors.groupingBy(SysDiffColumn::getTableName));


        Map<String, SysDiffTable> m1 = new HashMap<>();
        Map<String, SysDiffTable> m2 = new HashMap<>();

        for (SysDiffTable tableInfo : tables1) {
            m1.put(tableInfo.getTableName(), tableInfo);
        }
        for (SysDiffTable tableInfo : tables2) {
            m2.put(tableInfo.getTableName(), tableInfo);
        }
        //左边有 右边没有的
        List<SysDiffTable> tableInLeft = new ArrayList<>();
        //右边有 左边边没有的
        List<SysDiffTable> tableInRight = new ArrayList<>();
        //两边都有的
        List<SysDiffTable> tableBothLeft = new ArrayList<>();
        List<SysDiffTable> tableBothRight = new ArrayList<>();
        for (SysDiffTable tableInfo : tables1) {
            if (!m2.containsKey(tableInfo.getTableName())) {
                tableInLeft.add(tableInfo);
            } else {
                tableBothLeft.add(tableInfo);
            }
        }

        for (SysDiffTable tableInfo : tables2) {
            if (!m1.containsKey(tableInfo.getTableName())) {
                tableInRight.add(tableInfo);
            } else {
                tableBothRight.add(tableInfo);
            }
        }

        tableBothLeft.sort(Comparator.comparing(SysDiffTable::getTableName));
        tableBothRight.sort(Comparator.comparing(SysDiffTable::getTableName));


        //两边都有的表
        for (int i = 0; i < tableBothLeft.size(); i++) {
            ResultData.TableDiff tableDiff = calcTableDiff(tableBothLeft.get(i), tableBothRight.get(i));


            List<ResultData.ColumnDiff> columnDiffs = new ArrayList<>();


            String tableName = tableBothLeft.get(i).getTableName();
            List<SysDiffColumn> cols1 = group1.get(tableName);
            List<SysDiffColumn> cols2 = group2.get(tableName);

            cols1.sort(Comparator.comparing(SysDiffColumn::getColumnSort));
            cols2.sort(Comparator.comparing(SysDiffColumn::getColumnSort));

            Map<String, SysDiffColumn> mc1 = new HashMap<>();
            Map<String, SysDiffColumn> mc2 = new HashMap<>();

            for (SysDiffColumn columnInfo : cols1) {
                mc1.put(columnInfo.getColumnName(), columnInfo);
            }
            for (SysDiffColumn columnInfo : cols2) {
                mc2.put(columnInfo.getColumnName(), columnInfo);
            }

            //左边有 右边没有的列
            List<SysDiffColumn> columnInLeft = new ArrayList<>();
            //右边有 左边边没有的列
            List<SysDiffColumn> columnInRight = new ArrayList<>();
            //两边都有的列
            List<SysDiffColumn> columnBothLeft = new ArrayList<>();
            List<SysDiffColumn> columnBothRight = new ArrayList<>();

            for (SysDiffColumn columnInfo : cols1) {
                if (!mc2.containsKey(columnInfo.getColumnName())) {
                    columnInLeft.add(columnInfo);
                } else {
                    columnBothLeft.add(columnInfo);
                }
            }

            for (SysDiffColumn columnInfo : cols2) {
                if (!mc1.containsKey(columnInfo.getColumnName())) {
                    columnInRight.add(columnInfo);
                } else {
                    columnBothRight.add(columnInfo);
                }
            }

            Map<String, SysDiffColumn> leftColumnMapping = new HashMap<>();
            Map<String, SysDiffColumn> rightColumnMapping = new HashMap<>();
            for (SysDiffColumn columnInfo : columnBothLeft) {
                leftColumnMapping.put(columnInfo.getColumnName(), columnInfo);
            }
            for (SysDiffColumn columnInfo : columnBothRight) {
                rightColumnMapping.put(columnInfo.getColumnName(), columnInfo);
            }
            //两边都有的列
            for (SysDiffColumn columnInfo : columnBothLeft) {
                String columnName = columnInfo.getColumnName();
                ResultData.ColumnDiff columnDiff = calcColumnDiff(leftColumnMapping.get(columnName), rightColumnMapping.get(columnName));
                if (currCompareConfig.get().getOnlyDiff()) {
                    if (columnDiff.getDiffType() != DiffType.SAME) {
                        columnDiffs.add(columnDiff);
                    }
                } else {
                    columnDiffs.add(columnDiff);
                }
            }
            //仅左边存在的列
            for (SysDiffColumn columnInfo : columnInLeft) {
                columnDiffs.add(calcColumnDiff(columnInfo, null));
            }
            //仅右边存在的列
            for (SysDiffColumn columnInfo : columnInRight) {
                columnDiffs.add(calcColumnDiff(null, columnInfo));
            }

            //表的差异计数(表差异数+字段差异数)
            if (columnDiffs.stream().anyMatch(v -> v.getDiffType() != DiffType.SAME)) {
                tableDiff.setDiffType(DiffType.DIFF);
                int columnDiffCount = columnDiffs.stream().mapToInt(ResultData.ColumnDiff::getDiffCount).sum();
                int tableDiffCount = tableDiff.getDiffCount();
                tableDiff.setDiffCount((columnDiffCount + tableDiffCount));
            }

            tableDiff.getLeft().setColumnAttrs(columnDiffs.stream().map(ResultData.ColumnDiff::getLeft).collect(Collectors.toList()));
            tableDiff.getRight().setColumnAttrs(columnDiffs.stream().map(ResultData.ColumnDiff::getRight).collect(Collectors.toList()));

            tableDiff.getLeft().setColumnDiffs(columnDiffs);
            tableDiff.getRight().setColumnDiffs(columnDiffs);

            //是否仅看有差异的
            if (currCompareConfig.get().getOnlyDiff()) {
                if (tableDiff.getDiffType() == DiffType.SAME && tableDiff.getDiffCount() == 0) {
                    continue;
                }
            }
            tableDiffs.add(tableDiff);

        }
        //仅左边的表
        for (SysDiffTable tableInfo : tableInLeft) {
            ResultData.TableDiff tableDiff = calcTableDiff(tableInfo, null);

            String tableName = tableInfo.getTableName();
            List<SysDiffColumn> columnInLeft = group1.get(tableName);
            List<ResultData.ColumnDiff> columnDiffs = new ArrayList<>();
            for (SysDiffColumn columnInfo : columnInLeft) {
                columnDiffs.add(calcColumnDiff(columnInfo, null));
            }

            tableDiff.getLeft().setColumnAttrs(columnDiffs.stream().map(ResultData.ColumnDiff::getLeft).collect(Collectors.toList()));
            tableDiffs.add(tableDiff);

            tableDiff.getLeft().setColumnDiffs(new ArrayList<>());
            tableDiff.getRight().setColumnDiffs(new ArrayList<>());

        }
        //仅右边的表
        for (SysDiffTable tableInfo : tableInRight) {
            ResultData.TableDiff tableDiff = calcTableDiff(null, tableInfo);

            String tableName = tableInfo.getTableName();
            List<SysDiffColumn> columnInRight = group2.get(tableName);
            List<ResultData.ColumnDiff> columnDiffs = new ArrayList<>();
            for (SysDiffColumn columnInfo : columnInRight) {
                columnDiffs.add(calcColumnDiff(null, columnInfo));
            }
            tableDiff.getRight().setColumnAttrs(columnDiffs.stream().map(ResultData.ColumnDiff::getRight).collect(Collectors.toList()));
            tableDiffs.add(tableDiff);

            tableDiff.getLeft().setColumnDiffs(new ArrayList<>());
            tableDiff.getRight().setColumnDiffs(new ArrayList<>());
        }
        return tableDiffs;
    }


    private ResultData.DatasourceDiff calcDatasourceDiff(SysDiffDatasource d1, SysDiffDatasource d2) {
        ResultData.DatasourceAttr da1 = new ResultData.DatasourceAttr();
        ResultData.DatasourceAttr da2 = new ResultData.DatasourceAttr();
        LinkedHashSet<ResultData.DiffInstance> diff = new LinkedHashSet<>();
        Long countedTable1 = countTable(d1.getSysDiffDatasourceId());
        Long countedTable2 = countTable(d2.getSysDiffDatasourceId());
        diff.add(new ResultData.DiffInstance(DatasourceAttr.TABLE_COUNT.getAttrCode(), countedTable1, countedTable2, getDiffType(countedTable1, countedTable2)));
        diff.add(new ResultData.DiffInstance(DatasourceAttr.FUNCTION_COUNT.getAttrCode(), 0, 0, getDiffType(0, 0)));
        diff.add(new ResultData.DiffInstance(DatasourceAttr.PRODUCE_COUNT.getAttrCode(), 0, 0, getDiffType(0, 0)));

        da1.setDiff(diff);
        da2.setDiff(diff);
        da1.setName(d1.getName());
        da2.setName(d2.getName());
        da1.setDatabase(d1.getDatabaseName());
        da2.setDatabase(d2.getDatabaseName());
        da1.setTableCount(d1.getTableCount());
        da2.setTableCount(d2.getTableCount());
        ResultData.DatasourceDiff datasourceDiff = new ResultData.DatasourceDiff();
        datasourceDiff.setLeft(da1);
        datasourceDiff.setRight(da2);

        return datasourceDiff;
    }

    private Long countTable(Long datasourceId) {
        return tableInfoService.count(
                QueryWrapper.create().where(
                        SysDiffTableTableDef.SYS_DIFF_TABLE.SYS_DIFF_DATASOURCE_ID.eq(datasourceId)
                )
        );
    }

    private ResultData.TableDiff calcTableDiff(SysDiffTable t1, SysDiffTable t2) {

        String indexDetail1 = "";
        String indexDetail2 = "";

        ResultData.TableDiff tableDiff = new ResultData.TableDiff();

        ResultData.TableAttr ta1 = new ResultData.TableAttr();
        ResultData.TableAttr ta2 = new ResultData.TableAttr();
        if (t1 == null) {
            ta1.setDiffType(DiffType.LACK);
            tableDiff.setDiffType(DiffType.LACK);
        } else {
            indexDetail1 = JSONUtil.toJsonStr(t1.getTableIndexDetail());
            BeanUtils.copyProperties(t1, ta1);
            ta1.setTableIndexDetail(indexDetail1);
        }
        if (t2 == null) {
            ta2.setDiffType(DiffType.LACK);
            tableDiff.setDiffType(DiffType.LACK);
        } else {
            indexDetail2 = JSONUtil.toJsonStr(t2.getTableIndexDetail());
            BeanUtils.copyProperties(t2, ta2);
            ta2.setTableIndexDetail(indexDetail2);
        }

        LinkedHashSet<ResultData.DiffInstance> diff = new LinkedHashSet<>();
        diff.add(new ResultData.DiffInstance(TableAttr.TABLE_NAME.getAttrCode(), ta1.getTableName(), ta2.getTableName(), getDiffType(ta1.getTableName(), ta2.getTableName())));
        diff.add(new ResultData.DiffInstance(TableAttr.TABLE_TYPE.getAttrCode(), ta1.getTableType(), ta2.getTableType(), getDiffType(ta1.getTableType(), ta2.getTableType())));
        diff.add(new ResultData.DiffInstance(TableAttr.TABLE_ENGINE.getAttrCode(), ta1.getTableEngine(), ta2.getTableEngine(), getDiffType(ta1.getTableEngine(), ta2.getTableEngine())));
        diff.add(new ResultData.DiffInstance(TableAttr.TABLE_ROWS.getAttrCode(), ta1.getTableRows(), ta2.getTableRows(), getDiffType(ta1.getTableRows(), ta2.getTableRows())));
        diff.add(new ResultData.DiffInstance(TableAttr.TABLE_INDEX.getAttrCode(), ta1.getTableIndex(), ta2.getTableIndex(), getDiffType(ta1.getTableIndex(), ta2.getTableIndex())));
        diff.add(new ResultData.DiffInstance(TableAttr.TABLE_INDEX_DETAIL.getAttrCode(), indexDetail1, indexDetail2, getDiffType(indexDetail1, indexDetail2)));
        if (!currCompareConfig.get().getIgnoreTableComment()) {
            diff.add(new ResultData.DiffInstance(TableAttr.TABLE_COMMENT.getAttrCode(), ta1.getTableComment(), ta2.getTableComment(), getDiffType(ta1.getTableComment(), ta2.getTableComment())));
        }
        ta1.setDiff(diff);
        ta2.setDiff(diff);

        if (tableDiff.getDiffType() != DiffType.LACK) {
            if (diff.stream().allMatch(v -> v.getDiffType() == DiffType.SAME)) {
                ta1.setDiffType(DiffType.SAME);
                ta2.setDiffType(DiffType.SAME);
                tableDiff.setDiffType(DiffType.SAME);
            }
            if (diff.stream().anyMatch(v -> v.getDiffType() != DiffType.SAME)) {
                ta1.setDiffType(DiffType.DIFF);
                ta2.setDiffType(DiffType.DIFF);
                tableDiff.setDiffType(DiffType.DIFF);
                tableDiff.setDiffCount((int) diff.stream().filter(v -> v.getDiffType() != DiffType.SAME).count());
            }
        }
        tableDiff.setLeft(ta1);
        tableDiff.setRight(ta2);

        return tableDiff;
    }

    private ResultData.ColumnDiff calcColumnDiff(SysDiffColumn c1, SysDiffColumn c2) {
        ResultData.ColumnDiff columnDiff = new ResultData.ColumnDiff();

        ResultData.ColumnAttr ca1 = new ResultData.ColumnAttr();
        ResultData.ColumnAttr ca2 = new ResultData.ColumnAttr();
        if (c1 == null) {
            ca1.setDiffType(DiffType.LACK);
            columnDiff.setDiffType(DiffType.LACK);
            columnDiff.setDiffCount(1);
        } else {
            BeanUtils.copyProperties(c1, ca1);
        }
        if (c2 == null) {
            ca2.setDiffType(DiffType.LACK);
            columnDiff.setDiffType(DiffType.LACK);
            columnDiff.setDiffCount(1);
        } else {
            BeanUtils.copyProperties(c2, ca2);
        }

        LinkedHashSet<ResultData.DiffInstance> diff = new LinkedHashSet<>();
        diff.add(new ResultData.DiffInstance(ColumnAttr.COLUMN_NAME.getAttrCode(), ca1.getColumnName(), ca2.getColumnName(), getDiffType(ca1.getColumnName(), ca2.getColumnName())));
        diff.add(new ResultData.DiffInstance(ColumnAttr.COLUMN_TYPE.getAttrCode(), ca1.getColumnType(), ca2.getColumnType(), getDiffType(ca1.getColumnType(), ca2.getColumnType())));
        diff.add(new ResultData.DiffInstance(ColumnAttr.COLUMN_DEFAULT.getAttrCode(), ca1.getColumnDefault(), ca2.getColumnDefault(), getDiffType(ca1.getColumnDefault(), ca2.getColumnDefault())));
        if (!currCompareConfig.get().getIgnoreColumnNullable()) {
            diff.add(new ResultData.DiffInstance(ColumnAttr.COLUMN_NULL.getAttrCode(), ca1.getColumnNull(), ca2.getColumnNull(), getDiffType(ca1.getColumnNull(), ca2.getColumnNull())));
        }
        if (!currCompareConfig.get().getIgnoreColumnComment()) {
            diff.add(new ResultData.DiffInstance(ColumnAttr.COLUMN_COMMENT.getAttrCode(), ca1.getColumnComment(), ca2.getColumnComment(), getDiffType(ca1.getColumnComment(), ca2.getColumnComment())));
        }
        ca1.setDiff(diff);
        ca2.setDiff(diff);


        if (columnDiff.getDiffType() != DiffType.LACK) {
            if (diff.stream().allMatch(v -> v.getDiffType() == DiffType.SAME)) {
                ca1.setDiffType(DiffType.SAME);
                ca2.setDiffType(DiffType.SAME);
                columnDiff.setDiffType(DiffType.SAME);
            }
            if (diff.stream().anyMatch(v -> v.getDiffType() != DiffType.SAME)) {
                ca1.setDiffType(DiffType.DIFF);
                ca2.setDiffType(DiffType.DIFF);
                columnDiff.setDiffType(DiffType.DIFF);
                columnDiff.setDiffCount((int) diff.stream().filter(v -> v.getDiffType() != DiffType.SAME).count());
            }
        }

        columnDiff.setLeft(ca1);
        columnDiff.setRight(ca2);

        return columnDiff;
    }

    private DiffType getDiffType(Object o1, Object o2) {
        //其中一个为空
        if (Objects.isNull(o1) || Objects.isNull(o2)) {
            //如果都为空则按相同处理
            if (Objects.isNull(o1) && Objects.isNull(o2)) {
                return DiffType.SAME;
            }
            return DiffType.LACK;
        }
        if (!Objects.equals(o1, o2)) {
            return DiffType.DIFF;
        }
        return DiffType.SAME;
    }
}
